/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.rest.autoconfigure;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.tianyun.cloud.config.refresh.smart.annotation.SmartScope;
import org.tianyun.cloud.context.RequestContext.RequestContextHolder;
import org.tianyun.cloud.context.ServletContext.ContextOperator;
import org.tianyun.cloud.context.ServletContext.ServletContextHolder;
import org.tianyun.cloud.rest.configuration.RestTemplateProperties;

import javax.net.ssl.SSLContext;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;

/**
 * Web服务自动配置
 *
 * @auther ebert_chan
 */
@Configuration(proxyBeanMethods = false)
public class CloudRestAutoConfiguration {

    @Value("${spring.http.encoding.charset:UTF-8}")
    private String defaultCharset;

    @Bean
    @SmartScope(prefix = "rest.template")
    @ConfigurationProperties(prefix = "rest.template", ignoreInvalidFields = true)
    public RestTemplateProperties restTemplateProperties() {
        return new RestTemplateProperties();
    }

    @Bean
    @SmartScope("rest.template")
    public ClientHttpRequestFactory clientHttpRequestFactory(RestTemplateProperties restTemplateProperties)
            throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(restTemplateProperties.getReadTimeout());
        factory.setConnectTimeout(restTemplateProperties.getConnectTimeout());
        factory.setConnectionRequestTimeout(restTemplateProperties.getConnectTimeout());

        if (restTemplateProperties.getMaxConnTotal() != null && restTemplateProperties.getMaxConnTotal() > 0 && restTemplateProperties.getMaxConnPerRoute() != null
                && restTemplateProperties.getMaxConnPerRoute() > 0) {
            TrustStrategy acceptingTrustStrategy = (chain, authType) -> {
                return true;
            };
            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial((KeyStore) null, acceptingTrustStrategy).build();
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(restTemplateProperties.getConnectTimeout())
                    .setConnectionRequestTimeout(restTemplateProperties.getConnectTimeout()).setSocketTimeout(restTemplateProperties.getReadTimeout()).build();

            CloseableHttpClient httpClient = HttpClients.custom().setMaxConnTotal(restTemplateProperties.getMaxConnTotal())
                    .setMaxConnPerRoute(restTemplateProperties.getMaxConnPerRoute()).setSSLSocketFactory(csf).evictExpiredConnections().setDefaultRequestConfig(requestConfig)
                    .build();

            factory.setHttpClient(httpClient);
        }

        return factory;
    }

    @Bean
    public RestTemplateCustomizer defaultRestTemplateCustomizer(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplateCustomizer customizer = restTemplate -> {
            restTemplate.setRequestFactory(clientHttpRequestFactory);
            List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
            Iterator<?> iterator = messageConverters.iterator();
            while (iterator.hasNext()) {
                HttpMessageConverter<?> converter = (HttpMessageConverter<?>) iterator.next();
                if (converter instanceof StringHttpMessageConverter) {
                    StringHttpMessageConverter stringHttpMessageConverter = (StringHttpMessageConverter) converter;
                    stringHttpMessageConverter.setDefaultCharset(Charset.forName(defaultCharset));
                }
            }
        };
        return customizer;
    }

    @Bean
    public ClientHttpRequestInterceptor defaultClientHttpRequestInterceptor(RestTemplateProperties restTemplateProperties) {
        ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {

            boolean dispatch = CollectionUtils.isEmpty(restTemplateProperties.getContextTrustDomains());
            if (!dispatch) {
                String host = request.getURI().getHost();
                for (String domain : restTemplateProperties.getContextTrustDomains()) {
                    if (host.equals(domain) || host.endsWith(domain)) {
                        dispatch = Boolean.TRUE;
                        break;
                    }
                }
            }

            if (dispatch) {
                HttpHeaders headers = request.getHeaders();
                ContextOperator operator = (key, value) -> {
                    headers.add(key, value);
                };
                RequestContextHolder.dispatch(operator);
                ServletContextHolder.dispatch(operator);
            }

            return execution.execute(request, body);
        };
        return interceptor;
    }

    @Bean
    public RestTemplate restTemplate(ObjectProvider<List<RestTemplateCustomizer>> customizerProvider, ObjectProvider<List<ClientHttpRequestInterceptor>> interceptorProvider) {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();

        List<RestTemplateCustomizer> customizers = customizerProvider.getIfAvailable();
        List<ClientHttpRequestInterceptor> interceptors = interceptorProvider.getIfAvailable();
        if (customizers != null) {
            restTemplateBuilder = restTemplateBuilder.additionalCustomizers(customizers);
        }
        if (interceptors != null) {
            restTemplateBuilder = restTemplateBuilder.additionalInterceptors(interceptors);
        }
        RestTemplate restTemplate = restTemplateBuilder.build();

        return restTemplate;
    }

}
